/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * A.I. Secure Data File Class
 *      Provides an AI file format implementation for reading and writing 
 * secure (encrypted) data files.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using Adaptive.Foundation.Security;

namespace Adaptive.Foundation.IO
{
    /// <summary>
    /// Provides an AI file format implementation for reading and writing 
    /// secure (encrypted) data files.
    /// </summary>
    public class AiSecureFile : AiDataFileBase 
    {
        /*--------------------------------------------------------------------------------
         * Private Constants Declarations
         *------------------------------------------------------------------------------*/
        #region Private Constants Declarations

        private const string FILETYPE_NAME = "Secure Data File";
        private const string FILETYPE_DESC = "Adaptive Intelligence Encrypted Data Content";

        private const string BASE_APP_NAME = "Adaptive Intelligence Foundation Framework";
        private const string BASE_VER_STRING = "2.0.0.0";

        #endregion

        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Main AES Key and IV pair used to create key derivatives for encryption and decryption processes.
        /// </summary>
        private SymmetricKeyPair _pair;
        /// <summary>
        /// AES cryptographic utility.
        /// </summary>
        private AesCryptoDevice _device;
        /// <summary>
        /// Derived key set.
        /// </summary>
        private DerivedKeySetCollection _keySet = null;
        /// <summary>
        /// Current index of the key set to use.
        /// </summary>
        private int _keyIndex = 0;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class to the specified file name.
        /// </summary>
        /// <param name="fileName">
        /// The fully-qualified path and name of the file to be read or written.
        /// </param>
        public AiSecureFile(string fileName)
            : base(fileName)
        {
            _pair = new SymmetricKeyPair(CryptographicAlgorithm.Aes);
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_pair != null)
                    _pair.Dispose();
                if (_device != null)
                    _device.Dispose();
                if (_keySet != null)
                    _keySet.Dispose();
            }

            _pair = null;
            _keySet = null;
            _device = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the AES key value for the file.
        /// </summary>
        /// <value>
        /// A byte array containing an AES encryption/decryption key.
        /// </value>
        public byte[] Key
        {
            get { return _pair.Key; }
            set { _pair.Key = value; }
        }
        /// <summary>
        /// Gets or sets the AES IV value for the file.
        /// </summary>
        /// <value>
        /// A byte array containing an AES encryption/decryption IV.
        /// </value>
        public byte[] IV
        {
            get { return _pair.IV; }
            set { _pair.IV = value; }
        }
        /// <summary>
        /// Gets or sets the clear data content for the file.
        /// </summary>
        /// <value>
        /// A byte array containing the non-encrypted content.
        /// </value>
        public byte[] DataContent
        {
            get { return base.Data; }
            set { base.Data = value; }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Event Methods
         *------------------------------------------------------------------------------*/
        #region Protected Methods / Event Methods
        /// <summary>
        /// Processes the supplied block of data before it is written to the final content.
        /// </summary>
        /// <param name="originalData">
        /// A byte array read from a data block.
        /// </param>
        /// <returns>
        /// The processed data.
        /// </returns>
        /// <remarks>
        /// This can be implemented by classes which may perform encryption or decryption or other
        /// such operations on data after it is read from a data block.
        /// </remarks>
        protected override byte[] ProcessBlockAfterRead(byte[] originalData)
        {
            CryptographicOperation operation = null;                    //Cryptographic operation/
            MemoryStream stream = null;                                 //Stream data source.
            byte[] returnValue = null;                                  //Return value.
            
            //Set the AES device to the next key variant values.
            InitializeKeys();

            //Create the operation.
            stream = new MemoryStream(originalData);
            operation = new CryptographicOperation(CryptographicAlgorithm.Aes, CryptographicOperationType.Decrypt);
            operation.SourceData = stream;

            //Perform the operation.
            _device.PerformOperation(operation);
            if (operation.Result.Success)
            {
                //Translate and close streams.
                stream.Close();
                stream.Dispose();
                returnValue = StreamToBytes((VirtualMemoryStream)operation.Result.ResultData);
            }
            else
            {
                if (operation.Result.LastException != null)
                    throw new Exception(operation.Result.LastException.Message);
                else
                    throw new Exception();
            }
            operation.Dispose();

            return returnValue;
        }
        /// <summary>
        /// Processes the supplied block of data before it is written to the data block content.
        /// </summary>
        /// <param name="originalData">
        /// A byte array to write to a data block.
        /// </param>
        /// <returns>
        /// The processed data.
        /// </returns>
        /// <remarks>
        /// This can be implemented by classes which may perform encryption or decryption or other
        /// such operations on data before storing the data in a data block.
        /// </remarks>
        protected override byte[] ProcessBlockBeforeWrite(byte[] originalData)
        {
            CryptographicOperation operation = null;                    //Cryptographic operation/
            MemoryStream stream = null;                                 //Stream data source.
            byte[] returnValue = null;                                  //Return value.

            //Set the AES device to the next key variant values.
            InitializeKeys();

            //Create the operation.
            stream = new MemoryStream(originalData);
            operation = new CryptographicOperation(CryptographicAlgorithm.Aes, CryptographicOperationType.Encrypt);
            operation.SourceData = stream;

            //Perform the operation.
            _device.PerformOperation(operation);
            if (operation.Result.Success)
            {
                //Translate and close streams.
                stream.Close();
                stream.Dispose();
                returnValue = StreamToBytes((VirtualMemoryStream)operation.Result.ResultData);
            }
            else
            {
                if (operation.Result.LastException != null)
                    throw new Exception(operation.Result.LastException.Message);
                else
                    throw new Exception();
            }
            operation.Dispose();

            return returnValue;
        }
        /// <summary>
        /// Gets the name of the application.
        /// </summary>
        /// <returns>
        /// The name of the application implementing the file format.
        /// </returns>
        protected override  string GetApplicationName()
        {
            return BASE_APP_NAME;
        }
        /// <summary>
        /// Gets the description of the file format.
        /// </summary>
        /// <returns>
        /// A description of the implemented file format.
        /// </returns>
        protected override string GetFileTypeDescription()
        {
            return FILETYPE_DESC;
        }
        /// <summary>
        /// Gets the name of the file format.
        /// </summary>
        /// <returns>
        /// The name of the implemented file format.
        /// </returns>
        protected override string GetFileTypeName()
        {
            return FILETYPE_NAME;
        }
        /// <summary>
        /// Gets the implementing application version.
        /// </summary>
        /// <returns>
        /// A <see cref="Version"/> instance.
        /// </returns>
        protected override Version GetApplicationVersion()
        {
            return new Version(BASE_VER_STRING);
        }
        /// <summary>
        /// Gets the implemented file format version.
        /// </summary>
        /// <returns>
        /// A <see cref="Version"/> instance.
        /// </returns>
        protected override Version GetFileFormatVersion()
        {
            return new Version(BASE_VER_STRING);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Initializes the key set if neccessary and sets the current cryptographic device
        /// to the next item in the key set.
        /// </summary>
        private void InitializeKeys()
        {
            //Initialize key set, if not already done.
            if (_keySet == null)
            {
                _keyIndex = 0;
                _keySet = KeyVariantFactory.CreateAesDerivedKeySet(_pair.Key, _pair.IV, 64);
            }

            //Set the key derivatives and increment the key set counter.
            if (_device == null)
                _device = new AesCryptoDevice();

            _device.Key = _keySet[_keyIndex].Key;
            _device.IV = _keySet[_keyIndex].IV;
            _keyIndex++;
            if (_keyIndex >= _keySet.Count)
                _keyIndex = 0;
        }
        /// <summary>
        /// Translates the supplied stream instance to a byte array.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private byte[] StreamToBytes(VirtualMemoryStream stream)
        {
            MemoryStream storage = null;
            byte[] returnValue = null;

            storage = new MemoryStream();
            stream.CopyTo(storage);
            returnValue = storage.ToArray();
            storage.Close();

            return returnValue;
        }
        #endregion

    }
}